﻿using System;
using System.Net;
using System.Net.Sockets;

using COUP.Core;
using COUP.Core.Enums;
using COUP.Core.Networking;

namespace COUP.Auth.Networking
{
    public class ConnectionHandler
    {
        public event PacketEventHandler Handle;

        readonly Socket Connection;
        readonly IPEndPoint EndPoint;
        readonly int BufferSize;
        readonly int Port;

        public ConnectionHandler(int _Port = 9958)
        {
            //Declare Readonly Values
            Port = _Port;
            BufferSize = 1024;
            EndPoint = new IPEndPoint(IPAddress.Any, Port);
            Connection = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //

            Configure();
            Listen();

            Program.Output("Listening on Port " + _Port);
        }

        internal void Configure()
        {
            //Configures the Socket
            Connection.Bind(EndPoint);
            Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
            Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);              
        }

        internal void Listen()
        {
            //Begins listening on the socket for incomming connections
            Connection.Listen(100);
            Connection.BeginAccept(new AsyncCallback(onConnection), null);
        }       

        internal void onConnection(IAsyncResult res)
        {
            //Creates the ClientState object which will store all of our connection info
            ClientState State = new ClientState(Connection.EndAccept(res), BufferSize, ServerType.Auth, 0, null);
            //Resets the master socket to allow further connections
            Connection.BeginAccept(new AsyncCallback(onConnection), null);
            //Configures the ClientState socket
            State.Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
            State.Connection.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
            //Begins listening for data on the ClientState socket
            State.Connection.BeginReceive(State.Buffer, 0, BufferSize, SocketFlags.None,
                new AsyncCallback(onReceive), State);
        }

        internal void onReceive(IAsyncResult res)
        {
            SocketError socError;
            //Gets the state object
            ClientState State = (res.AsyncState as ClientState);
            //Checks if the state object exists
            if (State == null)
                return;
            //Checks if the state socket is connected
            if (!State.Connection.Connected)
                return;
            //Gets the state socket to be worked on locally
            Socket ClientConn = State.Connection;
            //Signals the socket to stop receiving and tell us how much data we need to handle
            int Recv = ClientConn.EndReceive(res, out socError);
            //Checks to see if we have data to handle (we should since onReceive was called
            //And checks there werent any errors on the socket, if there were we should abandon
            if (Recv == 0 || socError != SocketError.Success)
                return;
            //Creates an array for us to copy the received data to
            byte[] ReceivedData = new byte[Recv];
            //Performs a mass copy from the original buffer to the newly created array
            //And clears the buffer ready for the next set of data
            Buffer.BlockCopy(State.Buffer, 0, ReceivedData, 0, Recv);
            Buffer.SetByte(State.Buffer, 0, 0);
            //Decrypts the received data
            State.Cryptographer.Decrypt(ref ReceivedData);
            //Splits the packets up and invokes the handler          
            Handle.Invoke(State, new Packet(ReceivedData));
            //Resets the client socket to begin receiving more data
            State.Connection.BeginReceive(State.Buffer, 0, BufferSize, SocketFlags.None,
                new AsyncCallback(onReceive), State);
        }
    }
}
